import express from "express";
import puppeteer from "puppeteer";
import crypto from "crypto";
import path from "path";
import fs from "fs";
import { minify } from "html-minifier";

// cacheDir 与 main.js 同级目录
const cacheDir = path.join(path.dirname(new URL(import.meta.url).pathname), "cache");

// 从config.js中引入配置
import { port, timeout, addr, expire, compress } from "./config.js";

const app = express();
let browser, timeouter;

function scanAndDeleteCache(dir) {
  const now = Date.now();
  const files = fs.readdirSync(dir);
  files.forEach((file) => {
    const filePath = path.join(dir, file);
    const stats = fs.statSync(filePath);
    if (stats.isDirectory()) {
      scanAndDeleteCache(filePath);
    } else {
      const ctime = stats.ctimeMs;
      const etime = ctime + expire;
      if (now > etime) {
        ilog("Clear cache files.", filePath);
        fs.unlinkSync(filePath);
      }
    }
  });
}

function ilog(msg, ...args) {
  console.log(`[morender] ${new Date().toLocaleString()} - `, msg, ...args);
}

function clearCacheInterval() {
  // 扫描cache目录，删除过期文件
  try {
    scanAndDeleteCache(cacheDir);
  } catch (error) {
    ilog("Clear cache error.", error);
  }
  clearTimeout(timeouter);
  timeouter = setTimeout(() => {
    clearCacheInterval();
  }, expire);
}

function createCacheFile(cacheFile, content) {
  // 如果文件的文件夹不存在，则创建文件夹
  const dirname = path.dirname(cacheFile);
  if (!fs.existsSync(dirname)) {
    fs.mkdirSync(dirname, { recursive: true });
  }
  fs.writeFileSync(cacheFile, content);
}

function getCacheFilename(url) {
  // 计算url md5
  const md5 = crypto.createHash("md5");
  md5.update(url.toLowerCase());
  const key = md5.digest("hex");
  return `${cacheDir}/${key.split("").splice(0, 5).join("/")}/${key}.html`;
}

function getHost(url) {
  let u = String(url).toLowerCase();
  if (u.startsWith("https://") || u.startsWith("http://")) {
    return u.split("/")[2];
  }
  return "";
}

app.get("/ping", (req, res) => {
  res.send("Welcome!");
});

app.get("/*", async (req, res) => {
  // 判断资源请求类型
  if (
    // 以下页面前缀之外的页面，都不进行渲染
    !(
      req.path == "/" || // 首页
      req.path.startsWith("/category") || // 分类
      req.path.startsWith("/user") || // 用户主页
      req.path.startsWith("/article") || // 文章
      req.path.startsWith("/document") || // 文档
      req.path.startsWith("/search")
    )
  ) {
    res.redirect(`${addr}${req.url}`);
    return;
  }

  let url = `${addr}${req.url}`;

  let cachePath = "";
  if (expire > 0) {
    // 根据key生成缓存存储路径
    cachePath = getCacheFilename(url);
    // 尝试读取缓存
    try {
      // 判断缓存文件是否已过期
      const stats = fs.statSync(cachePath);
      const now = Date.now();
      // 缓存文件创建时间
      const ctime = stats.ctimeMs;
      // 缓存文件过期时间
      const etime = ctime + expire;
      // 缓存文件未过期
      if (now < etime) {
        const content = fs.readFileSync(cachePath, "utf8");
        res.send(content);
        return;
      }
    } catch (error) {
      ilog("Cache not found.", url);
    }
  }

  // 返回请求地址
  try {
    const start = Date.now();
    // 设置 SSR 服务为特有的 User-Agent
    const page = await browser.newPage();
    await page.setUserAgent("SSR Render Service");
    await page.goto(url, {
      waitUntil: ["networkidle2"],
      timeout: timeout,
    });
    let content = await page.content();
    await page.close();

    // 计算耗时
    const end = Date.now();
    // 年月日时分秒
    ilog(
      `耗时：${end - start}ms`,
      req.url,
      "=>",
      cachePath,
      "useragent =>",
      req.headers["user-agent"]
    );
    if (compress) {
      const compressStart = Date.now();
      content = minify(content, {
        removeComments: true,
        minifyCSS: true,
        minifyJS: true,
        includeAutoGeneratedTags: true,
        removeAttributeQuotes: true,
        removeComments: true,
        removeRedundantAttributes: true,
        removeScriptTypeAttributes: true,
        removeStyleLinkTypeAttributes: true,
        sortClassName: true,
        useShortDoctype: true,
        collapseWhitespace: true,
      });
      ilog(`压缩耗时：${Date.now() - compressStart}ms`);
    }

    if (expire > 0) {
      // 生成缓存文件
      createCacheFile(cachePath, content);
    }
    res.send(content);
  } catch (error) {
    ilog(error);
    res.status(500).send("Error");
  }
});

app.post("/render", async (req, res) => {
  // body内容： {"urls": ["http://example.com/page1", "http://example.com/page2"]}
  let body = "";
  req.on("data", (chunk) => {
    body += chunk.toString();
  });
  req.on("end", async () => {
    try {
      const { urls } = JSON.parse(body);
      if (!Array.isArray(urls) || urls.length === 0) {
        return res.status(400).send("Invalid input");
      }

      // key为url链接
      const results = [];
      // 设置 SSR 服务为特有的 User-Agent
      const page = await browser.newPage();
      await page.setUserAgent("SSR Render Service");
      await page.setRequestInterception(true);
      page.on("request", (request) => {
        // console.log(request.resourceType(), request.url())
        const host = getHost(request.url());
        if (host.endsWith("baidu.com") || host.endsWith("google.com")) {
          request.abort();
        } else if (request.resourceType() === "image" || request.resourceType() === "font") {
          request.abort();
        } else {
          request.continue(); // 其他资源正常加载
        }
      });
      for (const url of urls) {
        try {
          const start = Date.now();
          let link = url
          if (!url.startsWith('http')){
            link = addr + url
          }
          await page.goto(link, {
            waitUntil: ["networkidle2"],
            timeout: timeout,
          });
          const end = Date.now();
          console.log('render', link, "spend time", end - start, 'ms')
          let content = await page.content();
          results.push({
            url,
            content,
            spend: end - start + "ms",
          });
        } catch (error) {
          ilog(`Error rendering ${url}:`, error);
          results.push({
            url,
            error: error.message || "Rendering error",
          });
        }
      }
      page.close();
      res.json(results);
    } catch (error) {
      ilog(error);
      page.close();
      res.status(500).send("Error" + error.message);
    }
  });
});

app.listen(port, async () => {
  browser = await puppeteer.launch({
    headless: true,
    args: ["--no-sandbox", "--disable-setuid-sandbox"],
  });
  process.on("exit", async () => {
    ilog("close browser.");
    await browser.close();
  });
  if (expire > 0) {
    timeouter = setTimeout(() => {
      clearCacheInterval();
    }, expire);
  }
  ilog(`Server is running on port ${port}`);
});
